// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

#nullable enable

using System;
using System.Collections;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

using Microsoft.Scripting.Runtime;

using IronPython.Runtime;
using IronPython.Runtime.Exceptions;
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;

using static IronPython.Modules.PythonIOModule;


[assembly: PythonModule("termios", typeof(IronPython.Modules.PythonTermios), PlatformsAttribute.PlatformFamily.Unix)]
namespace IronPython.Modules;

[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
public static class PythonTermios {

    public const string __doc__ = """
        This module provides an interface to the Posix calls for tty I/O control.
        For a complete description of these calls, see the Posix or Unix manual
        pages. It is only available for those Unix versions that support Posix
        termios style tty I/O control.

        All functions in this module take a file descriptor fd as their first
        argument. This can be an integer file descriptor, such as returned by
        sys.stdin.fileno(), or a file object, such as sys.stdin itself.
        """;

    [SpecialName]
    public static void PerformModuleReload(PythonContext context, PythonDictionary dict)
        => context.EnsureModuleException("termioserror", dict, "error", "termios");


    #region Generated TIOC Commands

    // *** BEGIN GENERATED CODE ***
    // generated by function: generate_TIOC_commands from: generate_os_codes.py


    public static long TIOCCONS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x80047462 : 0x541d;

    public static int TIOCEXCL => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x2000740d : 0x540c;

    public static int TIOCGETD => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x4004741a : 0x5424;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCGICOUNT => 0x545d;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCGLCKTRMIOS => 0x5456;

    public static int TIOCGPGRP => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x40047477 : 0x540f;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCGSERIAL => 0x541e;

    [PythonHidden(PlatformID.Unix)]
    [SupportedOSPlatform("macos")]
    public static int TIOCGSIZE => 0x40087468;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCGSOFTCAR => 0x5419;

    public static int TIOCGWINSZ => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x40087468 : 0x5413;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCINQ => 0x541b;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCLINUX => 0x541c;

    public static long TIOCMBIC => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x8004746b : 0x5417;

    public static long TIOCMBIS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x8004746c : 0x5416;

    public static int TIOCMGET => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x4004746a : 0x5415;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCMIWAIT => 0x545c;

    public static long TIOCMSET => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x8004746d : 0x5418;

    public static int TIOCM_CAR => 0x40;

    public static int TIOCM_CD => 0x40;

    public static int TIOCM_CTS => 0x20;

    public static int TIOCM_DSR => 0x100;

    public static int TIOCM_DTR => 0x2;

    public static int TIOCM_LE => 0x1;

    public static int TIOCM_RI => 0x80;

    public static int TIOCM_RNG => 0x80;

    public static int TIOCM_RTS => 0x4;

    public static int TIOCM_SR => 0x10;

    public static int TIOCM_ST => 0x8;

    public static int TIOCNOTTY => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x20007471 : 0x5422;

    public static int TIOCNXCL => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x2000740e : 0x540d;

    public static int TIOCOUTQ => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x40047473 : 0x5411;

    public static long TIOCPKT => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x80047470 : 0x5420;

    public static int TIOCPKT_DATA => 0x0;

    public static int TIOCPKT_DOSTOP => 0x20;

    public static int TIOCPKT_FLUSHREAD => 0x1;

    public static int TIOCPKT_FLUSHWRITE => 0x2;

    public static int TIOCPKT_NOSTOP => 0x10;

    public static int TIOCPKT_START => 0x8;

    public static int TIOCPKT_STOP => 0x4;

    public static int TIOCSCTTY => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x20007461 : 0x540e;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSERCONFIG => 0x5453;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSERGETLSR => 0x5459;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSERGETMULTI => 0x545a;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSERGSTRUCT => 0x5458;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSERGWILD => 0x5454;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSERSETMULTI => 0x545b;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSERSWILD => 0x5455;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSER_TEMT => 0x1;

    public static long TIOCSETD => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x8004741b : 0x5423;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSLCKTRMIOS => 0x5457;

    public static long TIOCSPGRP => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x80047476 : 0x5410;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSSERIAL => 0x541f;

    [PythonHidden(PlatformID.Unix)]
    [SupportedOSPlatform("macos")]
    public static long TIOCSSIZE => 0x80087467;

    [PythonHidden(PlatformID.MacOSX)]
    [SupportedOSPlatform("linux")]
    public static int TIOCSSOFTCAR => 0x541a;

    public static long TIOCSTI => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x80017472 : 0x5412;

    public static long TIOCSWINSZ => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x80087467 : 0x5414;

    // *** END GENERATED CODE ***

    #endregion


    #region Other Public Constants
    // Linux: glibc/bits/termios.h (/usr/include/{x86_64,aarch64}-linux-gnu/)
    // macOS: usr/include/sys/termios.h (/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk)

    // iflag
    public const int IGNBRK  = 0x0001;    // ignore break condition
    public const int BRKINT  = 0x0002;    // signal interrupt on break
    public const int IGNPAR  = 0x0004;    // ignore characters with parity errors
    public const int PARMRK  = 0x0008;    // mark parity and framing errors
    public const int INPCK   = 0x0010;    // enable input parity check
    public const int ISTRIP  = 0x0020;    // mask off 8th bit
    public const int INLCR   = 0x0040;    // map NL into CR on input
    public const int IGNCR   = 0x0080;    // ignore CR
    public const int ICRNL   = 0x0100;    // map CR to NL on input
    public static int IXON   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
                               0x0200 : 0x0400;    // enable start output control
    public static int IXOFF  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
                               0x0400 : 0x1000;    // enable stop output control
    public const int IXANY   = 0x0800;    // any char will restart after stop
    public const int IMAXBEL = 0x2000;   // ring bell on input queue full
    public const int IUTF8   = 0x4000;     // maintain state for UTF-8 VERASE


    // oflag
    public const  int OPOST  = 0x0001;    // enable output processing
    public static int ONLCR => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?
                              0x0002 : 0x0004; // map NL to CR-NL


    // cflag
    public static int CSIZE  => CS5 | CS6 | CS7 | CS8; // number of bits per character
    public static int CS5    =>                                                   0x0000;           // 5 bits per character
    public static int CS6    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0100 : 0x0010;  // 6 bits per character
    public static int CS7    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0200 : 0x0020;  // 7 bits per character
    public static int CS8    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0300 : 0x0030;  // 8 bits per character
    public static int CREAD  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0800 : 0x0080;  // enable receiver
    public static int PARENB => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x1000 : 0x0100;  // parity enable
    public static int HUPCL  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x4000 : 0x0400;  // hang up on last close

    [PythonHidden(PlatformID.MacOSX)]
    public static int CBAUD  => 0x100f;  // mask for baud rate
    [PythonHidden(PlatformID.MacOSX)]
    public static int CBAUDEX => 0x1000;  // extra baud speed mask


    // lflag
    public static uint ECHOKE  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0001u : 0x0800u;       // visual erase for line kill
    public static uint ECHOE   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0002u : 0x0010u;       // visually erase chars
    public static uint ECHOK   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0004u : 0x0020u;       // echo NL after line kill
    public static uint ECHO    =>                                                   0x0008u;                // enable echoing of input characters
    public static uint ECHONL  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0010u : 0x0040u;       // echo NL even if ECHO is off
    public static uint ECHOPRT => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0020u : 0x0400u;       // visual erase mode for hardcopy
    public static uint ECHOCTL => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0040u : 0x0200u;       // echo control characters as ^(Char)
    public static uint ISIG    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0080u : 0x0001u;       // enable signals
    public static uint ICANON  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0100u : 0x0002u;       // canonical mode
    public static uint IEXTEN  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0400u : 0x8000u;       // enable extended input character processing
    public static uint TOSTOP  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0040_0000u : 0x0100u;  // stop background jobs from output
    public static uint FLUSHO  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x0080_0000u : 0x1000u;  // output being flushed
    public static uint PENDIN  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x2000_0000u : 0x4000u;  // retype pending input
    public static uint NOFLSH  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x8000_0000u : 0x0080u;  // don't flush after interrupt


    // when the changes take effect:
    public const int TCSANOW   = 0;     // change immediately
    public const int TCSADRAIN = 1;     // flush output, then change
    public const int TCSAFLUSH = 2;     // discard input, flush output, then change


    // control characters - indices into c_cc array
    public static int VEOF     => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?  0 : 4;
    public static int VEOL     => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?  1 : 11;
    public static int VEOL2    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?  2 : 16;
    public static int VERASE   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?  3 : 2;
    public static int VWERASE  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?  4 : 14;
    public static int VKILL    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?  5 : 3;
    public static int VREPRINT => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?  6 :  12;
    public static int VINTR    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?  8 :  0;
    public static int VQUIT    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ?  9 :  1;
    public static int VSUSP    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 10 : 10;
    public static int VSTART   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 12 :  8;
    public static int VSTOP    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 13 :  9;
    public static int VLNEXT   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 14 : 15;
    public static int VDISCARD => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 15 : 13;
    public static int VMIN     => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 16 :  6;
    public static int VTIME    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 17 :  5;
    [PythonHidden(PlatformID.MacOSX)]
    public static int VSWTC    => 7;
    public static int NCCS     => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 20 : 32;


    // control characters - character values
    public static int CINTR    = 0x03;   // ^C
    public static int CEOF     = 0x04;   // ^D
    public static int CEOT     = 0x04;   // ^D
    public static int CFLUSH   = 0x0f;   // ^O
    public static int CSTART   = 0x11;   // ^Q
    public static int CSTOP    = 0x13;   // ^S
    public static int CKILL    = 0x15;   // ^U
    public static int CLNEXT   = 0x16;   // ^V
    public static int CWERASE  = 0x17;   // ^W
    public static int CDSUSP   = 0x19;   // ^Y
    public static int CSUSP    = 0x1a;   // ^Z
    public static int CQUIT    = 0x1c;   // ^\
    public static int CERASE   = 0x7f;   // DEL
    public static int CEOL     => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0xff : 0x00;


    // tcflush() uses these
    public static int TCIFLUSH  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 1 : 0;     // flush input
    public static int TCOFLUSH  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 2 : 1;     // flush output
    public static int TCIOFLUSH => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 3 : 2;     // flush both


    // tcflow() uses these
    public static int TCOOFF => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 1 : 0;     // suspend output
    public static int TCOON  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 2 : 1;     // restart output
    public static int TCIOFF => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 3 : 2;     // transmit STOP character
    public static int TCION  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 4 : 3;     // transmit START character


    // baud rates
    public static int B0      => 0;
    public static int B50     => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 50 : 1;
    public static int B75     => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 75 : 2;
    public static int B110    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 110 : 3;
    public static int B134    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 134 : 4;
    public static int B150    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 150 : 5;
    public static int B200    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 200 : 6;
    public static int B300    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 300 : 7;
    public static int B600    => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 600 : 8;
    public static int B1200   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 1200 : 9;
    public static int B1800   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 1800 : 10;
    public static int B2400   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 2400 : 11;
    public static int B4800   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 4800 : 12;
    public static int B9600   => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 9600 : 13;
    public static int B19200  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 19200 : 14;
    public static int B38400  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 38400 : 15;
    public static int B57600  => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 57600 : 0x1001;
    public static int B115200 => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 115200 : 0x1002;
    public static int B230400 => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 230400 : 0x1003;
    // higher baud rates are not defined on macOS

    #endregion


    #region Public Functions

    [SupportedOSPlatform("macos")]
    [DllImport("libc", SetLastError = true, EntryPoint = "tcgetattr")]
    private static extern int _tcgetattr_darwin(int fd, out darwin__termios termios);

    [SupportedOSPlatform("linux")]
    [DllImport("libc", SetLastError = true, EntryPoint = "tcgetattr")]
    private static extern int _tcgetattr_linux(int fd, out linux__termios termios);

    [LightThrowing]
    public static object tcgetattr(CodeContext context, int fd) {
        CheckFileDescriptor(fd);

        var cc = new PythonList(NCCS);
        if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
            var termios = new darwin__termios();
            if (_tcgetattr_darwin(fd, out termios) == -1) {
                return LightExceptions.Throw(PythonNT.GetLastUnixError());
            }
            for (int i = 0; i < NCCS; i++) {
                unsafe { cc.Add(Bytes.FromByte(termios.c_cc[i])); }
            }
            return PythonList.FromArrayNoCopy([
                ToPythonInt(termios.c_iflag),
                ToPythonInt(termios.c_oflag),
                ToPythonInt(termios.c_cflag),
                ToPythonInt(termios.c_lflag),
                ToPythonInt(termios.c_ispeed),
                ToPythonInt(termios.c_ospeed),
                cc
            ]);
        } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
            unsafe { Console.WriteLine($"termios_size: {sizeof(linux__termios)}"); }
            var termios = new linux__termios();
            if (_tcgetattr_linux(fd, out termios) == -1) {
                return LightExceptions.Throw(PythonNT.GetLastUnixError());
            }
            for (int i = 0; i < NCCS; i++) {
                unsafe { cc.Add(Bytes.FromByte(termios.c_cc[i])); }
            }
            return PythonList.FromArrayNoCopy([
                ToPythonInt(termios.c_iflag),
                ToPythonInt(termios.c_oflag),
                ToPythonInt(termios.c_cflag),
                ToPythonInt(termios.c_lflag),
                ToPythonInt(termios.c_ispeed),
                ToPythonInt(termios.c_ospeed),
                cc
            ]);
        } else {
            throw new PlatformNotSupportedException();
        }
    }

    public static object tcgetattr(CodeContext context, object? file)
        => tcgetattr(context, PythonFcntl.GetFileDescriptor(context, file));


    [SupportedOSPlatform("macos")]
    [DllImport("libc", SetLastError = true, EntryPoint = "tcsetattr")]
    private static extern int _tcsetattr_darwin(int fd, int when, in darwin__termios termios);

    [SupportedOSPlatform("linux")]
    [DllImport("libc", SetLastError = true, EntryPoint = "tcsetattr")]
    private static extern int _tcsetattr_linux(int fd, int when, in linux__termios termios);

    [LightThrowing]
    public static object? tcsetattr(CodeContext context, int fd, int when, object? attributes) {
        CheckFileDescriptor(fd);

        if (attributes is not IList attrs || attrs.Count != 7) {
            throw PythonOps.TypeError("tcsetattr, arg 3: must be 7 element list");
        }

        if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
            var termios = new darwin__termios {
                c_iflag  = ToUInt64(attrs[InputFlagIdx]),
                c_oflag  = ToUInt64(attrs[OutputFlagIdx]),
                c_cflag  = ToUInt64(attrs[ControlFlagIdx]),
                c_lflag  = ToUInt64(attrs[LocalFlagIdx]),
                c_ispeed = ToUInt64(attrs[InputSpeedIdx]),
                c_ospeed = ToUInt64(attrs[OutputSpeedIdx])
            };

            IList chars = GetControlCharList(attrs);
            for (int i = 0; i < NCCS; i++) {
                unsafe { termios.c_cc[i] = GetControlChar(chars[i]); }
            }

            if (_tcsetattr_darwin(fd, when, in termios) == -1) {
                return LightExceptions.Throw(GetLastTermiosError(context));
            }
        } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
            var termios = new linux__termios {
                c_iflag  = (uint)ToUInt64(attrs[InputFlagIdx]),
                c_oflag  = (uint)ToUInt64(attrs[OutputFlagIdx]),
                c_cflag  = (uint)ToUInt64(attrs[ControlFlagIdx]),
                c_lflag  = (uint)ToUInt64(attrs[LocalFlagIdx]),
                c_ispeed = (uint)ToUInt64(attrs[InputSpeedIdx]),
                c_ospeed = (uint)ToUInt64(attrs[OutputSpeedIdx])
            };

            IList chars = GetControlCharList(attrs);
            for (int i = 0; i < NCCS; i++) {
                unsafe { termios.c_cc[i] = GetControlChar(chars[i]); }
            }

            if (_tcsetattr_linux(fd, when, in termios) == -1) {
                return LightExceptions.Throw(GetLastTermiosError(context));
            }
        } else {
            throw new PlatformNotSupportedException();
        }

#if NETCOREAPP
        // TODO: remove .NET workaround
        if (fd == 0
            && context.LanguageContext.SystemStandardIn is TextIOWrapper stdin
            && !stdin.closed
            && stdin.isatty(context)
            && stdin.fileno(context) == 0
            && !Console.IsInputRedirected) {

            if (when != TCSANOW) {
                stdin.flush(context);
            }

            ulong newLflag = ToUInt64(attrs[LocalFlagIdx]);
            if ((newLflag & (ECHO | ICANON | IEXTEN | ISIG)) == 0) {
                setraw(context, stdin);
            } else {
                setcbreak(context, stdin);
            }
        }
#endif

        return null;

        // Local functions ------------------------------------------------

        static IList GetControlCharList(IList attrs) {
            if (attrs[ControlCharsIdx] is not IList chars || chars.Count != NCCS) {
                throw PythonOps.TypeError("tcsetattr, atributes[{0}] must be {1} element list", ControlCharsIdx, NCCS);
            }
            return chars;
        }

        static byte GetControlChar(object? o) {
            if (o is Bytes b && b.Count == 1) {
                return (byte)b[0];
            }
            if (Converter.TryConvertToInt32(o, out int i)) {
                return (byte)i;
            }
            throw PythonOps.TypeError("tcsetattr: elements of attributes must be characters or integers");
        }

        static ulong ToUInt64(object? o)
            => PythonOps.TryToInt(o, out BigInteger index)
                ? (ulong)index
                : throw PythonOps.TypeErrorForBadInstance("tcsetattr: an integer is required (got type {0})", o);
    }

    [LightThrowing]
    public static object? tcsetattr(CodeContext context, object? file, int when, [NotNone] object attributes)
        => tcsetattr(context, PythonFcntl.GetFileDescriptor(context, file), when, attributes);


    [DllImport("libc", SetLastError = true, EntryPoint = "tcsendbreak")]
    private static extern int _tcsendbreak(int fd, int duration);

    [LightThrowing]
    public static object? tcsendbreak(CodeContext context, int fd, int duration) {
        CheckFileDescriptor(fd);
        if (_tcsendbreak(fd, duration) == -1) {
            return LightExceptions.Throw(GetLastTermiosError(context));
        }
        return null;
    }

    [LightThrowing]
    public static object? tcsendbreak(CodeContext context, object? fd, int duration)
        => tcsendbreak(context, PythonFcntl.GetFileDescriptor(context, fd), duration);


    [DllImport("libc", SetLastError = true, EntryPoint = "tcdrain")]
    private static extern int _tcdrain(int fd);

    [LightThrowing]
    public static object? tcdrain(CodeContext context, int fd) {
        CheckFileDescriptor(fd);
        if (_tcdrain(fd) == -1) {
            return LightExceptions.Throw(GetLastTermiosError(context));
        }
        return null;
    }

    [LightThrowing]
    public static object? tcdrain(CodeContext context, object? fd)
        => tcdrain(context, PythonFcntl.GetFileDescriptor(context, fd));


    [DllImport("libc", SetLastError = true, EntryPoint = "tcflush")]
    private static extern int _tcflush(int fd, int queue);

    [LightThrowing]
    public static object? tcflush(CodeContext context, int fd, int queue) {
        CheckFileDescriptor(fd);
        if (_tcflush(fd, queue) == -1) {
            return LightExceptions.Throw(GetLastTermiosError(context));
        }
        return null;
    }

    [LightThrowing]
    public static object? tcflush(CodeContext context, object? fd, int queue)
        => tcflush(context, PythonFcntl.GetFileDescriptor(context, fd), queue);


    [DllImport("libc", SetLastError = true, EntryPoint = "tcflow")]
    private static extern int _tcflow(int fd, int action);

    [LightThrowing]
    public static object? tcflow(CodeContext context, int fd, int action) {
        CheckFileDescriptor(fd);
        if (_tcflow(fd, action) == -1) {
            return LightExceptions.Throw(GetLastTermiosError(context));
        }
        return null;
    }

    [LightThrowing]
    public static object? tcflow(CodeContext context, object? fd, int action)
        => tcflow(context, PythonFcntl.GetFileDescriptor(context, fd), action);


    // Python 3.11: tcgetwinsize, tcsetwinsize

    [LightThrowing]
    public static object tcgetwinsize(CodeContext context, int fd) {
        var ws = new ushort[4];
        var buf = new MemoryBufferProtocolWrapper<ushort>(ws.AsMemory());

        object result = PythonFcntl.ioctl(fd, TIOCGWINSZ, buf, mutate_flag: true);

        if (ToTermiosError(context, result) is not null and var ex) {
            return ex;
        }
        return PythonTuple.MakeTuple((int)ws[0], (int)ws[1]);
    }

    [LightThrowing]
    public static object? tcgetwinsize(CodeContext context, object? fd)
        => tcgetwinsize(context, PythonFcntl.GetFileDescriptor(context, fd));


    [LightThrowing]
    public static object? tcsetwinsize(CodeContext context, int fd, object? winsize) {
        CheckFileDescriptor(fd);

        if (winsize is not IList wsList || wsList.Count != 2) {
            throw PythonOps.TypeError("tcsetwinsize, arg 2: must be a two-item sequence");
        }

        long winsize_0 = (long)PythonOps.ToIndex(wsList[0]);
        long winsize_1 = (long)PythonOps.ToIndex(wsList[1]);

        var ws = new ushort[4];
        var buf = new MemoryBufferProtocolWrapper<ushort>(ws.AsMemory());

        object result = PythonFcntl.ioctl(fd, TIOCGWINSZ, buf, mutate_flag: true);
        if (ToTermiosError(context, result) is not null and var ex) {
            return ex;
        }

        ws[0] = unchecked((ushort)winsize_0);
        ws[1] = unchecked((ushort)winsize_1);
        if (ws[0] != winsize_0 || ws[1] != winsize_1) {
            throw PythonOps.OverflowError("winsize value(s) out of range");
        }

        result = PythonFcntl.ioctl(fd, TIOCSWINSZ, buf);
        if (ToTermiosError(context, result) is not null and var ex2) {
            return ex2;
        }

        return null;
    }

    [LightThrowing]
    public static object? tcsetwinsize(CodeContext context, object? fd, object? winsize)
        => tcsetwinsize(context, PythonFcntl.GetFileDescriptor(context, fd), winsize);

    #endregion


    #region Termios struct
/*
Linux: termios-struct.h

typedef unsigned char	cc_t;
typedef unsigned int	speed_t;
typedef unsigned int	tcflag_t;

#define NCCS 32
struct termios
  {
    tcflag_t c_iflag;
    tcflag_t c_oflag;
    tcflag_t c_cflag;
    tcflag_t c_lflag;
    cc_t c_line;
    cc_t c_cc[NCCS];
    speed_t c_ispeed;
    speed_t c_ospeed;
  };
*/

    [SupportedOSPlatform("linux")]
    [StructLayout(LayoutKind.Sequential)]
    private unsafe struct linux__termios {
        public uint c_iflag;
        public uint c_oflag;
        public uint c_cflag;
        public uint c_lflag;
        public byte c_line;
        public fixed byte c_cc[32];
        public uint c_ispeed;
        public uint c_ospeed;
    }


/*
Darwin: termios.h
typedef unsigned long   tcflag_t;
typedef unsigned char   cc_t;
typedef unsigned long   speed_t;

#define NCCS            20
struct termios {
	tcflag_t        c_iflag;
	tcflag_t        c_oflag;
	tcflag_t        c_cflag;
	tcflag_t        c_lflag;
	cc_t            c_cc[NCCS];
	speed_t         c_ispeed;
	speed_t         c_ospeed;
};
*/

    [SupportedOSPlatform("macos")]
    [StructLayout(LayoutKind.Sequential)]
    private unsafe struct darwin__termios {
        public ulong c_iflag;
        public ulong c_oflag;
        public ulong c_cflag;
        public ulong c_lflag;
        public fixed byte c_cc[20];
        public ulong c_ispeed;
        public ulong c_ospeed;
    }

    // Python termios attributes
    private const int InputFlagIdx    = 0;
    private const int OutputFlagIdx   = 1;
    private const int ControlFlagIdx  = 2;
    private const int LocalFlagIdx    = 3;
    private const int InputSpeedIdx   = 4;
    private const int OutputSpeedIdx  = 5;
    private const int ControlCharsIdx = 6;

    #endregion


    #region Private Helpers

#if NETCOREAPP
    // .NET workaround for stdin raw mode
    private static object? _savedRawStdin;

    private static void setraw(CodeContext context, TextIOWrapper stdin) {
        if (_savedRawStdin is null && stdin.buffer is BufferedReader reader) {
            _savedRawStdin = reader.raw;
            reader.raw = new RawConsole(context);
        }
    }

    private static void setcbreak(CodeContext context, TextIOWrapper stdin) {
        if (_savedRawStdin is not null
            && stdin.buffer is BufferedReader reader
            && reader.raw is RawConsole) {

            reader.raw = _savedRawStdin;
            _savedRawStdin = null;
        }
    }

    private class RawConsole : _RawIOBase {
        public RawConsole(CodeContext context) : base(context) {
        }

        public override object? read(CodeContext context, object? size=null) {
            BigInteger intSize = PythonOps.ToIndex(size);
            if (intSize == 0) return null;

            ConsoleKeyInfo info = Console.ReadKey(intercept: true);
            return Bytes.FromByte(unchecked((byte)info.KeyChar));
        }

        public override int fileno(CodeContext context) => 0;
        public override bool isatty(CodeContext context) => true;
    }
#endif


    private static void CheckFileDescriptor(int fd) {
        if (fd < 0) {
            throw PythonOps.ValueError("file descriptor cannot be a negative integer ({0})", fd);
        }
    }


    private static object ToPythonInt(this ulong value)
        => value is <= int.MaxValue ? (int)value : (BigInteger)value;

    private static object? ToTermiosError(CodeContext context, object? error) {
        if (LightExceptions.GetLightException(error) is Exception ex) {
            var pex = ex.GetPythonException();
            if (pex is PythonExceptions._OSError oserr) {
                return LightExceptions.Throw(GetTermiosError(context, oserr.errno, oserr.strerror));
            } else {
                return error;
            }
        }
        return null;
    }

    private static Exception GetLastTermiosError(CodeContext context) {
        int errno = Marshal.GetLastWin32Error();
        return GetTermiosError(context, errno, PythonNT.strerror(errno));
    }


    private static Exception GetTermiosError(CodeContext context, object errno, object message)
        => PythonExceptions.CreateThrowable(termioserror(context), errno, message);


    private static PythonType termioserror(CodeContext context)
        => (PythonType)context.LanguageContext.GetModuleState("termioserror");

    #endregion
}
